package jmpo.mth;

import java.util.ArrayList;
import java.util.List;

/**
 * При работе потока могут возникать исключения.
 * Если не предпринимать никаких действий, то исключение приведёт к обрушению программы.
 * Однако из-за асинхронности исполнения потока мы не можем <<обернуть>> какую-то часть 
 * кода в try { } catch { } блок, чтобы поймать это исключение. 
 */
/**
 * В данном примере программа упадёт с исключением, несмотря на то, что мы обернули вызов
 * в try { } catch { }. Это связано с тем, что исключение возникает в другом потоке.
 */
final class UnckedExceptionExample {
    public static void test() {
        Thread t = new Thread(() -> {
            List<String> lst = new ArrayList<>();
            System.out.println(lst.get(0));
        });
        t.start();
    }
}

/**
 * В системе есть три уровня обработки исключений, которые покидают thread:
 *    уровень Thread
 *    уровень ThreadGroup
 *    уровень Default
 * 
 * Для того, чтобы поймать исключение, которое покинуло thread, нам нужно реализовать 
 * интерфейс Thread.UncaughtExceptionHandler, который содержит всего один метод 
 *    uncaughtException(Thread t, Throwable e)
 * 
 * Пример ниже демонстрирует перехват исключения, покинувшего поток.
 */
final class UncaughtExceptionHandlerExample {
    public static void test() {
        Thread t = new Thread(() -> {
            List<String> lst = new ArrayList<>();
            System.out.println(lst.get(0));
        });
        t.setUncaughtExceptionHandler((Thread tt, Throwable e) -> {
            System.out.format("Unexpected end of the thread \"%s\": %s\n", tt.getName(), e.getMessage());
        });
        t.start();
    }
}

/**
 * С помощью UncaughtExceptionHandler можно устанавливать различные состоияния <<ответа>>,
 * если поток должен вернуть результат, но в процессе работы возникает исключение.
 * Нам понадобится дополнительный объект, который будет отслеживать состояние завершения.
 * Затем мы внедрим ссылку этот объект в обработчик исключений.
 */
final class ResultWithExceptionExample {
    private static final class Result<T> {
        public Result() {
            this.t = null;
            this.good = true;
        }

        void setResult(T r) {
            this.r = r;
        }

        void setError(Throwable t) {
            this.good = false;
            this.t    = t;
        }

        T get() throws Throwable {
            if (!good) throw t;
            return r;
        }

        private volatile T         r;
        private volatile Throwable t;
        private volatile boolean   good;
    }

    public static void test(final int idx) {
        final Result<String> r = new Result<>();

        Thread t = new Thread(() -> {
            List<String> lst = new ArrayList<>();
            lst.add("Hello");
            r.setResult(lst.get(idx));
        });
        t.setUncaughtExceptionHandler((Thread tt, Throwable e) -> {
            r.setError(e);
        });
        t.start();
        try {
            t.join();
        } catch (InterruptedException e) {
            System.out.println("Unexpected interruption");
            return;
        }

        try {
            System.out.format("Result of the worker thread: %s\n", r.get());
        } catch(Throwable threadError) {
            System.out.format("Exception in the worker thread: %s\n", threadError.getMessage());
        }
    }
}

/**
 * Если внутри run() есть какой-либо код, который может выкинуть unchecked exception, то объявить это исключение
 * для методы run() невозможно.
 * Такие исключения следует поймать и завернуть в UncheckedException, чтообы они затем попали в ExceptionHandler.
 */

public class ThreadExceptionExample {
    public static void main(String[] args) {
        if (false)
            try {
                UnckedExceptionExample.test();
            } catch (Exception e) {
                System.out.println("Exception caught!");
                System.out.println(e.getMessage());
            }

        if (false) UncaughtExceptionHandlerExample.test();
        if (false) ResultWithExceptionExample.test(0); 
        if (false) ResultWithExceptionExample.test(1); 
    }
}
